﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using TracedReality.RayTracing;
using QiHe.Yaml.Grammar;

namespace TracedReality.Primitive
{
    /// <summary>
    /// An infinite plane.
    /// </summary>
    public class Plane : Shape
    {
        /// <summary>
        /// Point defining this plane.
        /// </summary>
        public Vector point;
        
        /// <summary>
        /// First direction defining the plane.
        /// </summary>
        public Vector d1;

        /// <summary>
        /// Second direction defining the plane.
        /// </summary>
        public Vector d2;

        /// <summary>
        /// The normal of this plane.
        /// </summary>
        public Vector normal
        {
            get
            {
                Vector cross = d1.cross(d2);
                cross.normalize();
                return cross;
            }
        }

        /// <summary>
        /// This is only used for inherited classes.
        /// Nothing else should call this.
        /// </summary>
        protected Plane()
        {
            point = null;
            d1 = null;
            d2 = null;
            material = null;
            bound = null;
        }

        /// <summary>
        /// Create a plane with the given point, direction Vectors, and Material.
        /// The direction Vectors are normalized.
        /// </summary>
        /// <param name="p">Point.</param>
        /// <param name="a">First direction.</param>
        /// <param name="b">Secon direction.</param>
        /// <param name="m">Material.</param>
        public Plane(Vector p, Vector a, Vector b, ref Material m)
        {
            point = p;
            d1 = a;
            d2 = b;
            material = m;

            d1.normalize();
            d2.normalize();

            // If the plane is not aligned along an axis this should end up being infinite in every direction
            Vector w = p + double.PositiveInfinity * d1 + double.PositiveInfinity * d2;
            Vector x = p + double.PositiveInfinity * d1 + double.NegativeInfinity * d2;
            Vector y = p + double.NegativeInfinity * d1 + double.NegativeInfinity * d2;
            Vector z = p + double.NegativeInfinity * d1 + double.PositiveInfinity * d2;
            Vector min = Vector.Min(z, Vector.Min(y, Vector.Min(x, w))) - Settings.EPSILON;
            Vector max = Vector.Max(z, Vector.Max(y, Vector.Max(x, w))) + Settings.EPSILON;
            bound = new BoundingBox(min, max);
        }

        public override bool Equals(Shape s)
        {
            if (s is Plane) { return Equals((Plane)s); }
            return false;
        }

        public bool Equals(Plane p)
        {
            return point.Equals(p.point) &&
                d1.Equals(p.d1) &&
                d2.Equals(p.d2) &&
                material.Equals(p.material);
        }

        public override bool Similar(Shape s)
        {
            if (!(s is Plane)) { return false; }
            Plane p = (Plane)s;

            return normal.Equals(p.normal) &&
                material.Equals(p.material);
        }

        /// <summary>
        /// Test if the given Ray intersects this Plane.
        /// </summary>
        /// <param name="ray">Ray to test for intersection.</param>
        /// <param name="hit">Hit to update with intersection info.</param>
        /// <returns>1 if the Ray intersects this Plane from the front,
        /// -1 if the Ray intersects this Plane from the back,
        /// 0 if no intersection occurs.</returns>
        public override int Intersect(Ray ray, Hit hit)
        {
            double d = ray.direction.dot(normal);

            if (d == 0) { return 0; }

            double n = (point - ray.origin).dot(normal);

            double t = n / d;

            if (t > Settings.EPSILON && t < hit.t)
            {
                hit.t = t;
                hit.normal = normal;
                hit.material = material;

                return (d < 0 ? 1 : -1);
            }

            return 0;
        }

        public static Plane Parse(MappingEntry entry)
        {
            if (entry == null) { throw new ArgumentNullException("entry"); }

            Mapping map = (Mapping)entry.Value;
            Material material = Material.Parse(map.Find("material"));
            MappingEntry found = map.Find("point");
            if (found == null) { throw new ArgumentException("No point entry"); }
            Vector point = Vector.FromSequence((Sequence)found.Value);
            found = map.Find("dir1");
            if (found == null) { throw new ArgumentException("No dir1 entry"); }
            Vector dir1 = Vector.FromSequence((Sequence)found.Value);
            found = map.Find("dir2");
            if (found == null) { throw new ArgumentException("No dir2 entry"); }
            Vector dir2 = Vector.FromSequence((Sequence)found.Value);

            return new Plane(point, dir1, dir2, ref material);
        }

        /// <summary>
        /// Get a string representation of this Plane.
        /// </summary>
        /// <returns>A string representing this Plane.</returns>
        public override string ToString()
        {
            return "Plane: { point: " + point + ", dir1: " + d1 + ", dir2: " + d2 + ", material: " + material + " }";
        }
    }
}
